package program;

import java.io.FileNotFoundException;
import java.util.HashMap;

import jsonreader.JSONParseException;
import libraries.EasyIn;
import optimizer.*;
import simulator.*;

public class Program {
	public static void main(String[] args) 
	{
		// Attempt to load the simulator using the units.json file which stores all information about units and buildings.
		Simulator simulator = new Simulator();
		try {
			simulator.loadFromJSON("units.json");
		}
		catch (FileNotFoundException | JSONParseException | StarcraftDataParseException e) {
			System.out.println("Unable to load the units.json file to parse unit and building data " + e.getMessage());
			return;
		}

		// Get the initial and target states
		GameState initialState = Program.getInitialState();
		GameState targetState = Program.getTargetState();
		
		// Attempt our basic strategy first, to guarantee a result even at very low attempts
		BasicOptimizerStrategy optimizertwo = new BasicOptimizerStrategy(targetState, initialState, simulator);
		Strategy simplestStrategy = optimizertwo.getTimeAndStrategyForSimplestBuildOrder();
		System.out.println("initial build time estimate: " + simplestStrategy.getTime() + "seconds");

		// The target class fills in dependencies into the minimumTarget which is used by the main optimizer strategy
		Target target = new Target(initialState, targetState);
		OptimizerMultiCore optimizer = new OptimizerMultiCore(simulator, target);
		
		// Some performance information is collected
		long time = System.currentTimeMillis();
		
		// The number of attempts to try the random strategy
		int numAttempts = getNumAttempts();
		
		// It is never better to run a cpu-intensive program such as this on more threads than the system has available
		int numThreads = Runtime.getRuntime().availableProcessors();
		
		// Run the strategy and wait for them to complete
		Strategy strategy = optimizer.run(numThreads, numAttempts, simplestStrategy.getTime());
		
		// Output some performance statistics (used for debugging)
		System.out.println("Performance: " + (System.currentTimeMillis() - time) + "ms in total. " + (System.currentTimeMillis() - time) / 400000.0 + "ms per loop");
		
		// if the strategy returned from out main optimizer is null, it never found a solution.
		// This should be fixable by giving it more time to compute a solution
		if(strategy == null) {
			System.out.println("Best time: " + simplestStrategy.getTime() + "seconds. Below are the build orders; ");
			simplestStrategy.printMoves();
		}
		else {
			System.out.println("Best time: " + strategy.getTime() + "seconds. Below are the build orders; ");
			strategy.printMoves();
		}
	}
	
	// The initial state is constant throughout, however it's editable anyway
	public static GameState getInitialState() {
		// Set the initial resources
		HashMap<GameAsset, Integer> initialResources = new HashMap<GameAsset, Integer>();
		initialResources.put(GameAsset.MINERAL, 50);
		initialResources.put(GameAsset.MINERAL_PATCH, 8);
		initialResources.put(GameAsset.VESPENE_GEYSER, 2);
		initialResources.put(GameAsset.WORKER, 6);
		initialResources.put(GameAsset.COMMAND_CENTER, 1);
		initialResources.put(GameAsset.SUPPLY, 4);
		
		return new GameState(initialResources);
	}
	
	// Get the target state, we could also do some console input/output here
	public static GameState getTargetState() {
		
		HashMap<GameAsset, Integer> targetResources = new HashMap<GameAsset, Integer>();
		for(GameAsset asset : GameAsset.values())
		{
			GameObject object = GameDataStore.getGameObjectFromGameAsset(asset);
			
			if(object != null)
			{
				System.out.println("Enter the number of " + object.getName() + " for target: ");
				targetResources.put(asset, EasyIn.getInt());
			}
		}
		/*targetResources.put(GameAsset.MARINE, 16);
		targetResources.put(GameAsset.HELLION, 8);
		targetResources.put(GameAsset.SIEGE_TANK, 10);
		targetResources.put(GameAsset.THOR, 2);*/
		return new GameState(targetResources);
	}
	
	// Get the number of attempts the user would like to run
	public static int getNumAttempts() {
		System.out.println("Enter the number of attempts to run: (1000 is a good start)");
		return EasyIn.getInt();
	}
}
